﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Drawing.Imaging;
using Game.Klassen;
using Game.Frames;
using System.IO;
using System.Drawing.Text;
using Game.Classes;



namespace Game
{
    public partial class controlGame : UserControl
    {
        #region Initialisieren

        //Hauptfenster
        Main fenster;

        //Image und Graphics Objekt zum Zeichnen
        Image       surface;
        Graphics    g;
        Image       pausepicture    = null;
        Image       endpicture      = null;
       
        //Konstruktor
        public controlGame(Main fenster, string mapfile)
        {
            //Hauptfenster merken
            InitializeComponent();
            this.fenster = fenster;

            //Bilder und Tileset Initialisieren
            surface = (Image)new Bitmap(pbGame.Width, pbGame.Height);
            g       = Graphics.FromImage(surface);

            //Spieler 1 Aktiv setzen
            Session.currentPlayer = Session.players[0];
            Session.currentPlayerIndex = 0;

            //Picturebox das Imageobjekt zuweisen
            pbGame.Image = surface;

            //Ton init
            if (Session.soundEnabled)
                fenster.playGameMusic();
            else
                lbSound.Image = Game.Properties.Resources.sound_disabled;

            //MessageTimer Init
            Session.hideMessage.Interval = 2000;
            Session.hideMessage.Tick += new EventHandler(resetMessage);

            //Map laden
            Session.loadMapFromFile(mapfile);

            //Font laden
            Session.loadFont();
            btMainMenu.Font = Session.buttonFont;
            btUnPause.Font = Session.buttonFont;
            btLevelSelection.Font = Session.buttonFont;
        }

        #endregion
        
        #region Bewegungen
        //Eingaben abfangen
        private void Game_KeyDown(object sender, PreviewKeyDownEventArgs e)
        {
            //Bewegung nur möglich wenn Spiel nicht pausiert ist
            if (Session.state == "play")
            {
                //Aktuelle Spielerposition
                Point pos = Session.currentPlayer.pos;

                //Mögliche Bewegungspositionen
                Point pUp       = new Point(pos.X, pos.Y - 1);
                Point pDown     = new Point(pos.X, pos.Y + 1);
                Point pLeft     = new Point(pos.X - 1, pos.Y);
                Point pRight    = new Point(pos.X + 1, pos.Y);

                //Spielerbewegung durch Pfeiltasten
                if (e.KeyCode == Keys.Up)
                    if (Tools.containsPlayer(pUp))
                        startFight(Session.currentPlayer, Tools.getPlayerbyPos(pUp));
                    else
                        Session.currentPlayer.move("up", true, true);

                if (e.KeyCode == Keys.Down)
                    if (Tools.containsPlayer(pDown))
                        startFight(Session.currentPlayer, Tools.getPlayerbyPos(pDown));
                    else
                        Session.currentPlayer.move("down", true, true);

                if (e.KeyCode == Keys.Left)
                    if (Tools.containsPlayer(pLeft))
                        startFight(Session.currentPlayer, Tools.getPlayerbyPos(pLeft));
                    else
                        Session.currentPlayer.move("left", true, true);

                if (e.KeyCode == Keys.Right)
                    if (Tools.containsPlayer(pRight))
                        startFight(Session.currentPlayer, Tools.getPlayerbyPos(pRight));
                    else
                        Session.currentPlayer.move("right", true, true);

            }

            //Mit leertaste runde beenden
            if (e.KeyCode == Keys.Space)
                Session.endTurn();
        }
        #endregion

        #region ButtonHover
        //Sound Button Hover
        private void btSound_MouseEnter(object sender, EventArgs e)
        {
            if (Session.soundEnabled)
                lbSound.Image = Game.Properties.Resources.sound_enabled_hover;
            else
                lbSound.Image = Game.Properties.Resources.sound_disabled_hover;
        }

        private void btSound_MouseLeave(object sender, EventArgs e)
        {
            if (Session.soundEnabled)
                lbSound.Image = Game.Properties.Resources.sound_enabled;
            else
                lbSound.Image = Game.Properties.Resources.sound_disabled;
        }

        private void btPause_MouseEnter(object sender, EventArgs e)
        {
            lbPause.Image = Game.Properties.Resources.pause_hover;
        }

        private void btPause_MouseLeave(object sender, EventArgs e)
        {
            lbPause.Image = Game.Properties.Resources.pause_normal;
        }

        private void btNextTurn_MouseEnter(object sender, EventArgs e)
        {
            lblEndTurn.Image = Game.Properties.Resources.nextTurn_hover;
        }

        private void btNextTurn_MouseLeave(object sender, EventArgs e)
        {
            lblEndTurn.Image = Game.Properties.Resources.nextTurn_normal;
        }

        private void btHover_MouseEnter(object sender, EventArgs e)
        {
            ((Button)sender).Image = Game.Properties.Resources.button_hover;
        }

        private void btHover_MouseLeave(object sender, EventArgs e)
        {
            ((Button)sender).Image = Game.Properties.Resources.button;
        }
        #endregion

        #region Tools & Events
        //Nachricht verbergen
        private void resetMessage(object sender, EventArgs e)
        {
            Session.infoMessage = String.Empty;
            Session.hideMessage.Enabled = false;
        }

        //Zum Hauptmenü gehen
        private void btgotoMenu_Click(object sender, EventArgs e)
        {
            this.Focus();
            this.fenster.showMenu();
        }

        //Zur Levelauswahl wechseln
        private void btLevelSelection_Click(object sender, EventArgs e)
        {
            this.Focus();
            this.fenster.showLevelAuswahl();
        }

        //Spiel fortsetzen
        private void btUnPause_Click(object sender, EventArgs e)
        {
            btUnPause.Visible = false;
            btMainMenu.Visible = false;

            lbPause.Visible = true;
            lbSound.Visible = true;
            lblEndTurn.Visible = true;

            Session.state = "play";
            this.Focus();
        }

        //Spiel Pausieren
        private void lbPause_Click(object sender, EventArgs e)
        {
            lbPause.Visible = false;
            lbSound.Visible = false;
            lblEndTurn.Visible = false;

            btUnPause.Visible = true;
            btMainMenu.Visible = true;

            pausepicture = null;
            Session.state = "pause";
        }

        //Runde Beenden
        private void lblEndTurn_Click(object sender, EventArgs e)
        {
            Session.endTurn();
        }

        //Ton Umschalten
        private void lbSound_Click(object sender, EventArgs e)
        {
            if (Session.soundEnabled)
            {
                fenster.stopGameMusic();
                Session.soundEnabled = false;
                lbSound.Image = Game.Properties.Resources.sound_disabled_hover;
            }
            else
            {
                fenster.playGameMusic();
                Session.soundEnabled = true;
                lbSound.Image = Game.Properties.Resources.sound_enabled_hover;
            }
        }



        //Kampf starten
        private void startFight(Player p1, Player p2)
        {
            //Kampf ist nur möglich wenn Spieler nicht schon einmal gekämpft hat diese Runde
            if (Session.currentPlayer.enabled && !Session.currentPlayer.alreadyFought)
            {
                pFight.Controls.Add(new controlFight(p1, p2, pFight));
                pFight.Visible = true;
            }
            else
                Session.setMessage("Du kannst nur einmal pro Runde angreifen!");
        }

        //Hintergrundfarbe ermitteln aufgrund der Spielerposition
        private Color getBackGroundColor()
        {
            //Spielerposition und Maphöhe
            int pY      = Session.currentPlayer.pos.Y;
            int height  = Session.map.Count();

            //Spielerposition kann nicht kleiner als 1 sein
            if (pY < 1)
                pY = 1;

            //Position prozential ausgedrückt
            double percentage = 1 - (((double)pY / (double)height) * 0.5);

            //Farbe berechnen
            int color_g = Convert.ToInt32(150 * percentage);
            int color_b = Convert.ToInt32(220 * percentage);

            //Limits für Farbe festlegen
            if (color_g < 60)
                color_g = 60;
            if (color_b < 110)
                color_b = 110;

            //Farbe zurückgeben
            return Color.FromArgb(0, color_g, color_b);
        }

        //Transparenz des Sonnenscheins ermitteln aufgrund der Spielerposition
        private ImageAttributes getShineTransparency()
        {
            //Aktuelle Spielerposition und Maphöhe
            int pY      = Session.currentPlayer.pos.Y;
            int height  = Session.map.Count();

            //Spielerposition darf nicht 0 sein
            if (pY < 1)
                pY = 1;

            //Position Prozentual ausgedrückt..
            double percentage = 1 - (((double)pY / (double)height) * 1.5);

            //Transparenz...
            ColorMatrix     cm = new ColorMatrix();
            ImageAttributes ia = new ImageAttributes();

            cm.Matrix33 = 1f * (float)percentage;
            ia.SetColorMatrix(cm);

            return ia;
        }

        //Bewegungsindikatorbild durch Kosten bekommen
        private Image getMovementIndicator(int cost)
        {
            if (cost == 1)
                return Game.Properties.Resources.move_1;
            if (cost == 2)
                return Game.Properties.Resources.move_2;
            if (cost == 3)
                return Game.Properties.Resources.move_3;
            else
                return Game.Properties.Resources.move_4;
        }

        //Update Objekte... falls disabled freigeben
        private void updateBgObjects()
        {
            for (int ix = 0; ix < Session.bgObjects.Count; ix++)
            {
                Session.bgObjects[ix].Update();
                if (!Session.bgObjects[ix].enabled)
                    Session.bgObjects.RemoveAt(ix);
            }
        }

        //"Sonnenstrahlen" zeichnen mit zunehmender Tiefer werden sie Transparenter
        private void drawShine()
        {
            g.DrawImage(Game.Properties.Resources.shine, new Rectangle(Session.currentPlayer.shinePos, 0, 1024, 768), 0, 0, 1024, 768, GraphicsUnit.Pixel, getShineTransparency());
        }

        //Benutzerinterface zeichnen
        private void drawGUI()
        {
            //Sauerstoffanzeige
            double percentage   = (Convert.ToDouble(Session.currentPlayer.oxygen) / 50f);
            int barHeight       = Convert.ToInt16(150 * percentage);

            //Sauerstoffanzeige
            g.DrawImage (Game.Properties.Resources.flasche, 20, 200             , 50, 200);
            g.DrawImage (Game.Properties.Resources.barFull, 46, 385 - barHeight , 10, barHeight);
            g.DrawString(Session.currentPlayer.oxygen + " / 50", Session.smallFont, Brushes.White, new PointF(15, 400));
            

            //Rundenanzeige
            g.DrawString("R u n d e " + Session.currentPlayer.turns, Session.smallFont, Brushes.White, new PointF(460, 20));

            //Inventar anzeigen
            if (Session.state != "ende")
                if (Session.currentPlayer.hasKey)
                    g.DrawImage(Game.Properties.Resources.inventoryFull, 924, 668, 90, 90);
                else
                    g.DrawImage(Game.Properties.Resources.inventoryEmpty, 924, 668, 90, 90);

            if (pFight.Visible == true)
                g.DrawImage(Game.Properties.Resources.dark, 0, 0, 1024, 768);
        }

        #endregion

        #region Zeichnen
        //Nachrichten Zeichnen
        private void drawMessage()
        {
            if (Session.hideMessage.Enabled)
            {
                StringFormat sf     = new StringFormat();
                sf.LineAlignment    = StringAlignment.Far;
                sf.Alignment        = StringAlignment.Center;
                g.DrawString(Session.infoMessage, Session.buttonFont, Brushes.White, new Rectangle(0, 0, 1024, 600), sf);
            }
        }

        //Zeichnet ein Feld an einer bestimmten Position
        private void drawTile(string type, int x, int y)
        {
            //TileID und Rotation aus Map lesen
            List<string> splitted   = Tools.readTile(type);
            string rotation         = splitted[1];
            string tileID           = splitted[0];

            //Tile aus Tilseset holen
            Tile drawingTile = Session.tileset.getTileByID(tileID);

            //Zeichnen
            if (drawingTile != null)
            {
                Image img   = (Image)drawingTile.getImage().Clone();
                img         = Tools.rotateImage(img, rotation);

                g.DrawImage(img, x * 64 + Session.currentPlayer.offset.X, y * 64 - Session.currentPlayer.offset.Y, 64, 64);
            }
        }

        //Zeichnet ein Feld der Hintergrundmap an einer Bestimmten Position
        private void drawBgTile(string type, int x, int y)
        {
            //TileID und Rotation aus Map lesen
            List<string> splitted   = Tools.readTile(type);
            string rotation         = splitted[1];
            string tileID           = splitted[0];

            //Tile aus Tilseset holen
            Tile drawingTile = Session.tileset.getTileByID(tileID);

            //Zeichnen
            if (drawingTile != null)
            {
                Image img   = (Image)drawingTile.getImage().Clone();
                img         = Tools.rotateImage(img, rotation);

                g.DrawImage(img, x * 64 + Session.currentPlayer.bgPos.X, y * 64 + Session.currentPlayer.bgPos.Y, 64, 64);
            }
        }

        //Zeichnet die Beiden Spieler
        private void renderPlayers()
        {
            foreach (Player player in Session.players)
            {
                //Spielerbild laden
                Image img = (Image)player.getImage();

                //Drehung der Spielergrafiken
                if (player.LastMoveDirection == "up")
                    img.RotateFlip(RotateFlipType.Rotate90FlipY);
                if (player.LastMoveDirection == "down")
                    img.RotateFlip(RotateFlipType.Rotate90FlipNone);
                if (player.LastMoveDirection == "left")
                    img.RotateFlip(RotateFlipType.RotateNoneFlipX);

                //Spieler auf der Grafik einzeichnen, falls sie im Sichtfeld liegen
                //Sichtfeld bestimmen
                int startX = Session.currentPlayer.pos.X - 7;  //Sichtbarer bereich fängt 7 Felder links vom Spieler a
                int startY = Session.currentPlayer.pos.Y - 5;  //Sichtbarer bereich fängt 5 Felder über dem Spieler an

                //Relative Position auf 0 setzen
                int relativPosX = 0;
                int relativPosY = 0;

                //Spieler zeichnen
                for (int y = startY; y < startY + 12; y++)
                {
                    relativPosX = 0;
                    for (int x = startX; x < startX + 16; x++)
                    {
                        int pX, pY;
                        if (new Point(x, y) == player.pos)
                        {
                            if (Session.currentPlayer != player)
                            {
                                pX = relativPosX * 64 + Session.currentPlayer.offset.X + player.offset.X;
                                pY = relativPosY * 64 - Session.currentPlayer.offset.Y + player.offset.Y;
                            }
                            else
                            {
                                pX = relativPosX * 64;
                                pY = relativPosY * 64;
                            }

                            g.DrawImage(img, new Rectangle(new Point(pX, pY), new Size(64, 64)));
                            g.DrawString(player.name, Session.tinyFont, Brushes.LightGray, pX, pY - 18);
                        }
                        relativPosX++;
                    }
                    relativPosY++;
                }

            }
        }

        //Hintergrund auf Graphics Zeichnen
        private void renderVisualbgmap()
        {
            for (int y = 0; y < 12; y++)
                for (int x = 0; x < 16; x++)
                    drawBgTile(Tools.getBgTile(new Point(x, y)), x, y);
        }

        //Vordergrund auf Graphics Zeichnen
        private void renderVisualmap()
        {
            //Relative Position (aus Sicht des Spielers bestimmen)
            int startX = Session.currentPlayer.pos.X - 8;  //Sichtbarer bereich fängt 8 Felder links vom Spieler an
            int startY = Session.currentPlayer.pos.Y - 6;  //Sichtbarer bereich fängt 6 Felder über dem Spieler an

            //Flächen außerhalb der Map übermalen
            if (startX < 0)
            {
                Rectangle r = new Rectangle(-64+Session.currentPlayer.offset.X, 0, (startX) * 64 * -1, 770);
                g.FillRectangle(new SolidBrush(Color.FromArgb(1,65,93)), r);
            }
            if (startX+17 >= Session.map[0].Length)
            {
                int overFlow = (startX + 16) - Session.map[0].Length + 1;
                Rectangle r = new Rectangle((16-(startX+16-Session.map[0].Length+1))*64+Session.currentPlayer.offset.X, 0, overFlow*64-Session.currentPlayer.offset.X, 770);
                g.FillRectangle(new SolidBrush(Color.FromArgb(1, 65, 93)), r);
            }
            if (startY < 0)
            {
                Rectangle r = new Rectangle(0,0, 1024, (startY)*-1*64-64-Session.currentPlayer.offset.Y);
                g.FillRectangle(new SolidBrush(Color.FromArgb(1, 65, 93)), r);
            }
            if (startY+13 >= Session.map.Length)
            {
                int overFlow = (startY + 12) - Session.map.Length + 1;
                Rectangle r = new Rectangle(0, (12 - (startY + 12 - Session.map.Length + 1)) * 64 - Session.currentPlayer.offset.Y, 1024, overFlow * 64 + Session.currentPlayer.offset.Y);
                g.FillRectangle(new SolidBrush(Color.FromArgb(1, 65, 93)), r);
            }

            //Relative Position auf -1 Setzen (1 weiter als Sichtbereich für Bewegungen)
            int relativPosX = -1;
            int relativPosY = -1;

            //Tile von absoluter Position an Relativer Position Zeichnen
            for (int y = startY; y < startY + 14; y++)
            {
                relativPosX = -1;
                for (int x = startX; x < startX + 18; x++)
                {
                    drawTile(Tools.getTile(new Point(x, y)), relativPosX, relativPosY);
                    relativPosX++;
                }
                relativPosY++;
            }
        }

        //Hintergrundobjekte  Zeichnen
        private void drawBackgroundObjects()
        {
            //Sichtfeld des aktuellen Spielers bestimmen
            int startX = Session.currentPlayer.pos.X - 7;  //Sichtbarer bereich fängt 7 Felder links vom Spieler a
            int startY = Session.currentPlayer.pos.Y - 5;  //Sichtbarer bereich fängt 5 Felder über dem Spieler an

            //Relative Position auf 0 setzen
            int relativPosX = 0;
            int relativPosY = 0;

            //Objekt an relativer Position einzeichnen
            for (int y = startY; y < startY + 12; y++)
            {
                relativPosX = 0;
                for (int x = startX; x < startX + 16; x++)
                {
                    foreach (BackGroundObject obj in Session.bgObjects)
                    {
                        if (new Point(x, y) == obj.position)
                        {
                            //Position berechnen
                            int pX, pY;
                            pX = relativPosX * 64 + obj.offset.X + Session.currentPlayer.offset.X;
                            pY = relativPosY * 64 + obj.offset.Y - Session.currentPlayer.offset.Y;

                            //Objekt Zeichnen
                            g.DrawImage(obj.getImage(), new Rectangle(new Point(pX, pY), new Size(obj.size.Width, obj.size.Height)));
                        }
                    }
                    relativPosX++;
                }
                relativPosY++;
            }
        }

        //Hintergrundobjekte  Zeichnen
        private void drawForegroundObjects()
        {
            foreach (ForeGroundObject fgobj in Session.fgObjects)
            {
                fgobj.Update();
                g.DrawImage(fgobj.getImage(), fgobj.position.X, fgobj.position.Y, fgobj.size.Width, fgobj.size.Height);
            }
        }

        //Bewegungspfeile Anzeigen
        public void drawMovementIndicator()
        {
            //Nur Anzeigen wenn Spieler Steuern kann
            if (Session.currentPlayer.enabled == true)
            {
                //Aktuelle Spielerpositon beziehen. Anschließend Positionen um ihn herum bestimmen
                Point pos = Session.currentPlayer.pos;

                //Positionen um den Spieler herum
                Point Pup       = new Point(pos.X, pos.Y - 1);
                Point Pdown     = new Point(pos.X, pos.Y + 1);
                Point Pleft     = new Point(pos.X - 1, pos.Y);
                Point Pright    = new Point(pos.X + 1, pos.Y);

                //Bild für die jeweilige Kostenstufe der Bewegung laden
                Image up    = getMovementIndicator(Session.getMoveCost(Pup));
                Image down  = getMovementIndicator(Session.getMoveCost(Pdown));
                Image left  = getMovementIndicator(Session.getMoveCost(Pleft));
                Image right = getMovementIndicator(Session.getMoveCost(Pright));
                Image fight = Game.Properties.Resources.move_fight;

                if (Session.currentPlayer.alreadyFought)
                    fight = Game.Properties.Resources.move_nofight;

                //Pfeilgrafiken für die jeweilige Richtung drehen
                down.RotateFlip (RotateFlipType.RotateNoneFlipY);
                left.RotateFlip (RotateFlipType.Rotate90FlipX);
                right.RotateFlip(RotateFlipType.Rotate90FlipNone);

                //Bewegungsindikatoren werden nur angezeigt wenn auf dem jeweiligen Feld keine Kollision ist
                if (Tools.containsPlayer(Pup))
                    g.DrawImage(fight, 7 * 64 + 5, 4 * 64, 45, 45);
                else
                    if (!Tools.positionCollision(Pup))
                        g.DrawImage(up, 7 * 64 + 5, 4 * 64, 45, 45);

                if (Tools.containsPlayer(Pdown))
                    g.DrawImage(fight, 7 * 64 + 5, 6 * 64 + 10, 45, 45);
                else
                    if (!Tools.positionCollision(Pdown))
                        g.DrawImage(down, 7 * 64 + 5, 6 * 64 + 10, 45, 45);

                if (Tools.containsPlayer(Pleft))
                    g.DrawImage(fight, 6 * 64, 5 * 64 + 10, 45, 45);
                else
                    if (!Tools.positionCollision(Pleft))
                        g.DrawImage(left, 6 * 64, 5 * 64 + 10, 45, 45);

                if (Tools.containsPlayer(Pright))
                    g.DrawImage(fight, 8 * 64 + 10, 5 * 64 + 10, 45, 45);
                else
                    if (!Tools.positionCollision(Pright))
                        g.DrawImage(right, 8 * 64+10, 5 * 64 + 10, 45, 45);
            }
        }
        #endregion

        //Animations Timer
        private void animateTick_Tick(object sender, EventArgs e)
        {
            foreach (BackGroundObject bgObj in Session.bgObjects)
            {
                bgObj.AnimateTick();
            }
        }

        // Game Loop
        private void gameLoop_Tick(object sender, EventArgs e)
        {

            if (Session.state == "play")
            {
                //Update Objekte... falls disabled freigeben
                updateBgObjects();

                //Fläche mit Hintergrundfarbe füllen
                g.Clear(getBackGroundColor());

                //HintergrundSession.map zeichnen
                renderVisualbgmap();

                //"Sonnenstrahlen" zeichnen mit zunehmender Tiefer werden sie Transparenter
                drawShine();

                //Hintergrund Objekte
                drawBackgroundObjects();

                //Spielerpositions unabhängige Objekte
                drawForegroundObjects();

                //Vordergrund zeichnen
                renderVisualmap();

                //Spieler zeichnen
                renderPlayers();

                //Benutzerinterface zeichnen
                drawGUI();

                //Bewegungsrichtungen mit Kosten anzeigen
                drawMovementIndicator();

                //Nachrichten anzeigen
                drawMessage();
            }

            //Wenn Spiel pausiert ist, Pauseschirm rendern
            else if (Session.state == "pause")
            {
                if (pausepicture == null)
                {
                    pausepicture = pbGame.Image;
                    g.DrawImage(Game.Properties.Resources.pause, 0, 0, 1024, 768);
                }
                  
                g.DrawImage(pausepicture, 0, 0);
            }


            //Wenn Sieger gefunden wurde, Endeschirm anzeigen
            else if (Session.state == "ende")
            {
                if (endpicture == null)
                {
                    //Interface elemente verstecken
                    lblEndTurn.Visible  = false;
                    lbPause.Visible     = false;
                    lbSound.Visible     = false;
                    btMainMenu.Visible  = false;

                    //Hauptmenübutton anzeigen
                    btLevelSelection.Visible  = true;
                    endpicture          = pbGame.Image;

                    g.DrawImage (Game.Properties.Resources.ende, 0, 0, 1024, 768);
                    g.DrawString(Session.currentPlayer.name + " gewinnt!", Session.buttonFont, Brushes.White, new PointF(370, 340));
                }

                //Endschirm zeichnen
                g.DrawImage(endpicture, 0, 0);
            }

            //Image der Picturebox zuweisen
            pbGame.Image = surface;
        }

        

        

    }
}
